AspectJ形式AOP使用
本文相关代码(来自官方源码spring-test模块)请参见spring-framework org.springframework.mylearntest包下。
无需像1.0那样实现相应的接口,唯一要做的就是在这个Aspect类上加一个@Aspect注解。这样就可以判断ClassPath中哪些类是我们要找的Aspect定义。通过@Pointcut定义Pointcut,通过Around等注解来指定哪些方法定义了相应的Advice逻辑。
PerformanceTraceAspect
package org.springframework.mylearntest.aop2.aspectj;
import org.apache.commons.lang3.time.StopWatch;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
/**
* @Author: whalefall
* @Date: 2020/7/28 22:46
* Spring 2.0 之后无需实现接口定义pointcut
*/
@Aspect
public class PerformanceTraceAspect {
private final Log logger = LogFactory.getLog(PerformanceTraceAspect.class);
@Pointcut("execution(public void *.method1()) || execution(public void *.method2())")
public void pointcutName() {}
@Around("pointcutName()")
public Object performanceTrace(ProceedingJoinPoint joinPoint) throws Throwable {
StopWatch sw = new StopWatch();
try {
sw.start();
return joinPoint.proceed();
} finally {
System.out.println("pt in method["
+ joinPoint.getSignature().getName()
+ "]>>>>>>" + sw.toString());
}
}
}
两种织入Aspect方法
假如我们有一个目标对象Foo,有两种方式将Aspect定义织入这个目标对象类,,实现对其符合Pointcut定义的Joinpoint进行拦截。
Foo
public class Foo {
public void method1() {
System.out.println("method1 executed");
}
public void method2() {
System.out.println("method2 executed");
}
}
编程方式织入
通过AspectJProxyFactory实现
public class Test4AspectJProxyFactory {
public static void main(String[] args) {
AspectJProxyFactory weaver = new AspectJProxyFactory();
weaver.setProxyTargetClass(true);
weaver.setTarget(new Foo());
weaver.addAspect(PerformanceTraceAspect.class);
Object proxy = weaver.getProxy();
((Foo)proxy).method1();
((Foo)proxy).method2();
}
}
通过自动代理织入
针对@AspectJ风格的AOP,Spring AOP专门提供了一个AutoProxyCreator实现类进行自动代理,以免去过多的编码和配置工作,它是在AbstractAdvisorAutoProxyCreator基础上的一个扩展类。
与AutoProxyCreator一样,我们需要在IoC容器的配置文件中注册一下AnnotationAwareAspectJAutoProxyCreator就可以了。
xml 配置
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:aspectj-autoproxy proxy-target-class="true"/>
<!-- 等同于上面一行-->
<!--<bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator">
<property name="proxyTargetClass" value="true"/>
</bean>-->
<bean id="performanceAspect" class="org.springframework.mylearntest.aop2.aspectj.PerformanceTraceAspect"/>
<bean id="target" class="org.springframework.mylearntest.aop2.aspectj.Foo"/>
</beans>
TestAutoAspectJ
public class Test4AutoAspectJ {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("annotationawareaspectJautoproxycreator\\annotationawareaspectJautoproxycreator.xml");
Object proxy = context.getBean("target");
((Foo)proxy).method1();
((Foo)proxy).method2();
}
}
如果把target作为依赖对象注入其他的bean定义,那么依赖的主体对象在持有的也是被代理过的目标对象。
Tip
在使用@AspectJ形式的AOP的时候,应该尽量使用容器内的自动代理支持,通常,只有处于测试目的才会使用编程的方式直接织入操作,在使用过程中,你会发现,实际上这两种方式是有差异的,一些行为并不统一。
使用@Aspect形式的AOP,需要引入aspectjweaver.jar和aspectjrt.jar。
@AspectJ形式的Pointcut
在Spring框架发布 2.0 版本之前,Spring AOP没有像AspectJ那样的正式的Pointcut描述语言,而且也只支持方法级别的拦截。所以通常情况下,简单的方法名指定以及正则表达式两种,基本上可以很好地达到目的。
在Spring发布 2.0 版本之后,Spring AOP 框架集成了AspectJ的部分功能,这其中就包括AspectJ的Pointcut语言支持。从DTD过渡到XSD时代。
@AspectJ形式Pointcut声明方式
@AspectJ形式的Pointcut声明,依附在@ApectJ,通过使用org.aspectj.lang.annotation.Pointcut这个注解,指定AspectJ形式的Pointcut 表达式之后,将这个指定了相应表达式的注解标注到Aspect定义类的某个方法上即可。
@AspectJ形式的Pointcut声明包含如下两个部分。
- Pointcut Expression
Pointcut Expression的载体为@Pointcut,该注解是方法级别的注解,所以Pointcut Expression不能脱离某个方法单独声明
表达式由两部分组成,分别是Pointcut标识符,和表达式匹配模式。
- Pointcut Signature
它是一个方法的定义,作为Pointcut Expression的载体。Pointcut Signature所在的方法定义,除了返回类型必须是void之外,没有其他的限制。方法修饰符所起到的作用于java语言中语义相同,public型的Pointcut Signature可以在其他Aspect定义中引用,private则只能在当前Aspect定义中引用。可以将Pointcut Signature作为相应Pointcut Expression的标识符,在Pointcut Expression的定义中取代重复的Pointcut表达式 定义。
YourAspect
@Aspect
public class YourAspect {
@Pointcut("execution(void method1())")
public void method1Execution() {}
@Pointcut("method1Execution()")
private void stillMethod1Execution() {}
// ...
}
AspectJ形式Pointcut表达式的标识符
execution
使用它将帮助我们匹配拥有指定方法签名的Joinpoint,使用格式如下
execution(modifiers-pattern ? ret-type-pattern declaring-type-pattern ? name-pattern(param-pattern) throws-pattern?)
其中方法的返回类型、方法名、以及参数部分匹配模式时必须指定的,其他部分的匹配模式可以省略。
Foo Example
public class Foo {
public void doSomething(String arg) {
// ...
}
}
那么可以指定如下Pointcut表达式来匹配Foo的doSomething方法
execution(public void Foo.doSomething(String))
execution(void Foo.doSomething(String))
*可以用于任何部分的匹配模式中,可以匹配相邻的多个字符*****
execution(* *(String))
execution(* *(*))
**..**通配符可以在两个位置使用
- declaring-type-pattern处使用指定多个层次的类型声明
// 只能定位到cn.spring21这一层下的所有类型
execution(void cn.spring21.*.doSomething(*)
// 可以匹配到所有的cn.spring21包下的包括子孙包
execution(void cn.spring21..*.doSomething(*)
- 用于方法列表匹配位置,则表示该方法可以有0到多个参数,参数类型不限。如果..换成*,则只能匹配一个参数。
execution(void *.doSomething(..))
// 表示第一个参数为String,第二个参数为任意类型
execution(void doSomething(String,*))
// 表示不限参数格式,不限类型,但是最后一个参数类型必须是String
execution(void doSomething(..,String))
within
within标识符只接受类类型的声明,它将会匹配指定类型下所有的Joinpoint方法。
within切点表达式写法
within(cn.spring21.aop.target.MockTarget)
// 匹配target包下所有类内部的方法级别的Joinpoint
within(cn.spring21.aop.target.*)
// 匹配aop包下以及子孙包下所有类内部方法级别的Joinpoint
within(cn.spring21.aop..*)
within切点表达式使用
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface AnyJoinpointAnnotation {}